package com.wyz.socket.test.server;

import com.wyz.socket.core.Common;
import com.wyz.socket.core.base.Context;
import com.wyz.socket.core.base.packet.string.StringReceivePacket;
import com.wyz.socket.core.impl.chain.AbstractStringPacketChain;
import com.wyz.socket.core.impl.Connector;
import com.wyz.socket.core.impl.scheduler.IdleTimeoutSchedulerJob;
import com.wyz.socket.core.impl.scheduler.SchedulerJob;
import com.wyz.socket.core.impl.chain.AbstractCloseChain;
import com.wyz.socket.core.impl.ConnectorHandler;
import com.wyz.socket.core.utils.Log;
import com.wyz.socket.test.room.AudioRoom;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.*;
import java.util.*;
import java.util.concurrent.TimeUnit;

public class TCPServer implements ServerSocketListener.AcceptCallback {
    private final int port;
    private ServerSocketListener listener;

    private Selector selector;
    private ServerSocketChannel server;

    private final Map<String, ConnectorHandler> connectorHandlerMaps = new HashMap<>();
    private final Map<ConnectorHandler, ConnectorHandler> cmdHandlerToAudioHandler = new HashMap<>();
    private final Map<ConnectorHandler, ConnectorHandler> audioHandlerToCmdHandler = new HashMap<>();
    private final Map<ConnectorHandler, AudioRoom> audioHandlerToRoom = new HashMap<>();
    private final Map<String, AudioRoom> keyToRoom = new HashMap<>();

    public TCPServer(int port) {
        this.port = port;
    }

    public boolean start() {
        try {
            selector = Selector.open();
            ServerSocketChannel server = ServerSocketChannel.open();
            server.configureBlocking(false);
            server.socket().bind(new InetSocketAddress(port));
            server.register(selector, SelectionKey.OP_ACCEPT);
            this.server = server;
            Log.printlnInfo("TCP-Server", "服务器信息：" + server.getLocalAddress().toString());
            ServerSocketListener listener = this.listener = new ServerSocketListener(selector, this);
            listener.start();
        } catch (IOException e) {
            e.printStackTrace();
            return false;
        }
        return true;
    }

    /**
     * 系统通知功能,通知所有的链接
     */
    public synchronized void broadcast(String str) {
        ConnectorHandler[] connectorHandlers;
        synchronized (connectorHandlerMaps) {
            connectorHandlers = connectorHandlerMaps.values().toArray(new ConnectorHandler[0]);
        }
        for (ConnectorHandler item : connectorHandlers) {
            item.send(str);
        }
    }

    /**
     * 转发功能
     */
    public void sendMessageToOtherClient(ConnectorHandler handler, String msg) {
        // 异步提交转发任务
        Context.get().getScheduler().execute(() -> {
            synchronized (connectorHandlerMaps) {
                Collection<ConnectorHandler> values = connectorHandlerMaps.values();
                for (ConnectorHandler connectorHandler : values) {
                    if (connectorHandler.equals(handler)) {
                        // 跳过自己
                        continue;
                    }
                    // 对其他客户端发送消息
                    connectorHandler.send(msg);
                }
            }
        });
    }

    public void sendMessageToMine(ConnectorHandler handler, String msg) {
        // 异步提交转发任务
        Context.get().getScheduler().execute(() -> handler.send(msg));
    }


    /**
     * 接收到新的socketChannel 回调
     */
    @Override
    public void accept(ConnectorHandler connectorHandler) {
        connectorHandler.getCloseChain().addChain(
                new RemoveAudioQueueOnConnectorCloseChannel()
        ).addChain(new AbstractCloseChain() {
            @Override
            protected boolean consume(ConnectorHandler handler, Connector connector) {
                Log.printlnInfo("Connection", "CloseChannel : " + handler.getClientInfo() + " [CLOSE]");
                synchronized (connectorHandlerMaps) {
                    connectorHandlerMaps.remove(handler.key.toString());
                }
                return true;
            }
        }); // 关闭的责任链
        connectorHandler.getPrintChain().addChain(
                new ParseAudioCommandStringPacketChain()
        ).addChain(new AbstractStringPacketChain() {
            @Override
            protected boolean consume(ConnectorHandler handler, StringReceivePacket stringReceivePacket) {
                String entity = stringReceivePacket.entity();
                Log.printlnInfo("Connection", "StringChain : " + entity);
                sendMessageToOtherClient(handler, entity);
                return true;
            }
        }); // 打印责任链
        SchedulerJob job = new IdleTimeoutSchedulerJob(30, TimeUnit.SECONDS, connectorHandler);
        connectorHandler.schedule(job);  // 创建一个心跳事件
        String connectorKey = connectorHandler.key.toString();
        // 添加同步处理
        synchronized (connectorHandlerMaps) {
            connectorHandlerMaps.put(connectorKey, connectorHandler);
        }
        // 回送一个数据
        sendMessageToMine(connectorHandler, Common.COMMAND_INFO_NAME + connectorKey);
    }


    /**
     * 回调,用于确认依赖对象以及进行关闭操作,并通知上层进行最后收尾
     */
    @Override
    public <T> void onClose(T t) {
        if (t instanceof ServerSocketListener) {
            try {
                selector.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                server.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    @Override
    public void close() {
        if (listener != null) {
            try {
                listener.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        ConnectorHandler[] connectorHandlers;
        synchronized (connectorHandlerMaps) {
            connectorHandlers = connectorHandlerMaps.values().toArray(new ConnectorHandler[0]);
            connectorHandlerMaps.clear();
        }
        for (ConnectorHandler item : connectorHandlers) {
            item.close();
        }
    }


    public class ParseAudioCommandStringPacketChain extends AbstractStringPacketChain {
        @Override
        protected boolean consume(ConnectorHandler handler /*命令流*/, StringReceivePacket stringReceivePacket) {
            String entity = stringReceivePacket.entity();
            Log.printlnDebug("ParseAudioCommandStringPacketChain" , entity);
            if (entity.startsWith(Common.COMMAND)) {
                if (entity.startsWith(Common.COMMAND_CONNECTOR_BIND)) {
                    Log.printlnDebug("ParseAudioCommandStringPacketChain" , "COMMAND_CONNECTOR_BIND");
                    String key = entity.substring(Common.COMMAND_CONNECTOR_BIND.length());
                    ConnectorHandler audioHandler = findConnectFromKey(key);
                    if (audioHandler != null) {
                        cmdHandlerToAudioHandler.put(handler, audioHandler);
                        audioHandlerToCmdHandler.put(audioHandler, handler);
                        audioHandler.changeSocketModeToBridge();
                    }
                } else if (entity.startsWith(Common.COMMAND_AUDIO_CREATE_ROOM)) {
                    Log.printlnDebug("ParseAudioCommandStringPacketChain" , "COMMAND_AUDIO_CREATE_ROOM");
                    ConnectorHandler audioHandler = findAudioStreamConnector(handler);
                    if (audioHandler != null) {
                        AudioRoom room = createNewRoom();
                        joinRoom(room, audioHandler);
                        sendMessageToMine(handler, Common.COMMAND_INFO_AUDIO_ROOM + room.getRoomCode());
                    }
                } else if (entity.startsWith(Common.COMMAND_AUDIO_LEAVE_ROOM)) {
                    Log.printlnDebug("ParseAudioCommandStringPacketChain" , "COMMAND_AUDIO_LEAVE_ROOM");
                    ConnectorHandler audioConnector = findAudioStreamConnector(handler);
                    if (audioConnector != null) {
                        recycleRoom(audioConnector);
                        sendMessageToMine(handler, Common.COMMAND_INFO_AUDIO_STOP);
                    }
                } else if (entity.startsWith(Common.COMMAND_AUDIO_JOIN_ROOM)) {
                    Log.printlnDebug("ParseAudioCommandStringPacketChain" , "COMMAND_AUDIO_JOIN_ROOM");
                    ConnectorHandler audioHandler = findAudioStreamConnector(handler);
                    if (audioHandler != null) {
                        String roomCode = entity.substring(Common.COMMAND_AUDIO_JOIN_ROOM.length());
                        AudioRoom room = keyToRoom.get(roomCode);
                        if (room != null && joinRoom(room, audioHandler)) {
                            ConnectorHandler otherAudioHandler = room.getOtherHandler(audioHandler);
                            otherAudioHandler.bindToBridge(audioHandler.sender());
                            audioHandler.bindToBridge(otherAudioHandler.sender());

                            sendMessageToMine(handler, Common.COMMAND_INFO_AUDIO_START);
                            ConnectorHandler otherCmdHandler;
                            synchronized (audioHandlerToCmdHandler) {
                                otherCmdHandler = audioHandlerToCmdHandler.get(otherAudioHandler);
                            }
                            if (otherCmdHandler != null) {
                                sendMessageToMine(otherCmdHandler, Common.COMMAND_INFO_AUDIO_START);
                            }
                        } else {
                            sendMessageToMine(handler, Common.COMMAND_INFO_AUDIO_ERROR);
                        }
                    }
                }
            } else {
                return false;
            }
            return true;
        }
    }

    class RemoveAudioQueueOnConnectorCloseChannel extends AbstractCloseChain {

        @Override
        protected boolean consume(ConnectorHandler handler, Connector connector) {
            if (cmdHandlerToAudioHandler.containsKey(handler)) {
                cmdHandlerToAudioHandler.remove(handler);
            } else if (audioHandlerToCmdHandler.containsKey(handler)) {
                audioHandlerToCmdHandler.remove(handler);
                recycleRoom(handler);
            }
            return false;
        }
    }

    private void recycleRoom(ConnectorHandler audioHandler) {
        AudioRoom room;
        synchronized (audioHandlerToRoom) {
            room = audioHandlerToRoom.get(audioHandler);
        }
        if (room == null) {
            return;
        }
        ConnectorHandler[] connectors = room.getConnectors();
        for (ConnectorHandler otherhandler : connectors) {
            otherhandler.unBindBridge();
            synchronized (audioHandlerToRoom) {
                audioHandlerToRoom.remove(otherhandler);
            }
            if (otherhandler != audioHandler) {
                ConnectorHandler otherCmdHandler = null;
                synchronized (audioHandlerToCmdHandler) {
                    otherCmdHandler = audioHandlerToCmdHandler.get(otherhandler);
                }
                sendMessageToMine(otherCmdHandler, Common.COMMAND_INFO_AUDIO_STOP);
                sendMessageToMine(otherhandler, Common.COMMAND_INFO_AUDIO_STOP);
            }
        }
        keyToRoom.remove(room.getRoomCode());
    }

    private boolean joinRoom(AudioRoom room, ConnectorHandler audioHandler) {
        if (room.joinRoom(audioHandler)) {
            synchronized (audioHandlerToRoom) {
                audioHandlerToRoom.put(audioHandler, room);
                return true;
            }
        }
        return false;
    }

    private AudioRoom createNewRoom() {
        AudioRoom room;
        do {
            room = new AudioRoom();
        } while (keyToRoom.get(room.getRoomCode()) != null);
        keyToRoom.put(room.getRoomCode(), room);
        return room;
    }

    private ConnectorHandler findAudioStreamConnector(ConnectorHandler handler) {
        ConnectorHandler audioHandler = null;
        synchronized (cmdHandlerToAudioHandler) {
            audioHandler = cmdHandlerToAudioHandler.get(handler);
        }
        if (audioHandler == null) {
            sendMessageToMine(handler, Common.COMMAND_INFO_AUDIO_ERROR);
            return null;
        }
        return audioHandler;
    }

    private ConnectorHandler findConnectFromKey(String key) {
        synchronized (connectorHandlerMaps) {
            return connectorHandlerMaps.get(key);
        }
    }
}
